package cn.seecoder.ai.serviceImpl;

import cn.seecoder.ai.dao.FileFieldRepository;
import cn.seecoder.ai.dao.FileInfoRepository;
import cn.seecoder.ai.exception.AIExternalException;
import cn.seecoder.ai.model.po.FileField;
import cn.seecoder.ai.model.po.FileInfo;
import cn.seecoder.ai.model.vo.FileInfoVO;
import cn.seecoder.ai.service.FileAnalysisService;
import cn.seecoder.ai.service.FileInfoService;
import cn.seecoder.ai.service.FileScalaService;
import cn.seecoder.ai.utils.HdfsHelper;
import cn.seecoder.ai.utils.POJOConverter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author fanyanpeng
 * @date 2023/4/11 20:46
 */
@Slf4j
@Service
public class FileInfoServiceImpl implements FileInfoService {
    @Autowired
    private FileFieldRepository fileFieldRepository;


    @Autowired
    HdfsHelper hdfsHelper;

    @Autowired
    FileInfoRepository fileInfoRepository;


    @Autowired
    FileScalaService fileScalaService;

    @Autowired
    FileAnalysisService fileAnalysisService;


    /**
     * 添加文件到hdfs
     * 注意，本服务提供并不支持文件分析，仅将文件远程保存。
     *
     * @param fileName 新命名的文件名
     * @param userId   用户id
     * @param file     文件
     * @return cn.seecoder.ai.model.vo.FileInfoVO
     * @author fanyanpeng
     * @date 2023/4/11 20:39
     */
    @Override
    public FileInfoVO addFile(String fileName, Integer userId, MultipartFile file) {
        String originalFilename = file.getOriginalFilename();
        log.info("文件原名："+originalFilename);
        log.info("文件大小："+file.getSize()+"B");

        // 支持上传文件类型为csv，其他类型报错。
        if(!originalFilename.endsWith(".csv")){
            throw AIExternalException.fileTypeNotSupported();
        }

        ensureFileNameUnique(userId, fileName);

        String uploadFileUri = hdfsHelper.getUriUploadFile(userId, fileName);
        hdfsHelper.saveMultipartFile(file,uploadFileUri);

        return createFile(uploadFileUri,fileName,userId);
    }

    /**
     * 根据数据库导入数据，
     * 注意，本服务提供并不支持文件分析，仅将文件远程保存。
     *
     * @return cn.seecoder.ai.model.vo.ResultVO<cn.seecoder.ai.model.vo.FileInfoVO>
     * @author fanyanpeng
     * @date 2023/4/11 20:45
     */
    @Override
    public FileInfoVO addTable(String url,
                               String user,
                               String password,
                               String tableName,
                               String fileName,
                               Integer userId) {

        ensureFileNameUnique(userId,fileName);
        String fileUri = hdfsHelper.getUriUploadFile(userId, fileName);
        fileScalaService.saveTable(fileUri,url,user,password,tableName);

        return createFile(fileUri,fileName,userId);
    }

    FileInfoVO createFile(String uploadFileUri,String fileName,Integer userId){
        FileInfo fileInfo = FileInfo.builder()
                .originalFileUri(uploadFileUri)
                .fileName(fileName)
                .uploadTime(new Date())
                .userId(userId)
                .build();

        fileInfoRepository.save(fileInfo);
        // 尽管在此处调用了重新获取，若jpa使用了缓存机制，并不会真正重新获取。
        return getFileInfo(userId,fileInfo.getId());
    }

    private void ensureFileNameUnique(Integer userId,String fileName){
        Optional<FileInfo> fileInfoOptional= fileInfoRepository.findByUserIdAndFileName(userId,fileName);
        if(fileInfoOptional.isPresent()){
            throw AIExternalException.fileNameAlreadyExistForThisUser();
        }
    }




    private FileInfo toPO(FileInfoVO fileInfoVO){
        FileInfo fileInfo = (FileInfo) POJOConverter.convert(fileInfoVO);
        return fileInfo;
    }

    private FileInfoVO toVO(FileInfo fileInfo){
        FileInfoVO fileInfoVO = (FileInfoVO) POJOConverter.convert(fileInfo);
        return fileInfoVO;
    }

    /**
     * 获取文件信息，包括各字段信息
     *
     * @param userId
     * @param fileInfoId
     * @return cn.seecoder.ai.model.vo.FileInfoVO
     * @author fanyanpeng
     * @date 2023/4/11 20:43
     */
    @Override
    public FileInfoVO getFileInfo(Integer userId, Integer fileInfoId) {
        Optional<FileInfo> fileInfoOptional= fileInfoRepository.findById(fileInfoId);
        if(!fileInfoOptional.isPresent()){
            throw AIExternalException.fileNotFoundByFileInfoId();
        }
        FileInfo fileInfo = fileInfoOptional.get();

        return toVO(fileInfo);
    }

    /**
     * 获取某用户的所有文件信息
     *
     * @param userId
     * @return cn.seecoder.ai.model.vo.ResultVO<java.util.List < cn.seecoder.ai.model.vo.FileInfoVO>>
     * @author fanyanpeng
     * @date 2023/4/11 20:44
     */
    @Override
    public List<FileInfoVO> getFileInfoListByUserId(Integer userId) {
        return fileInfoRepository.findByUserIdOrderByUploadTimeDesc(userId).stream().map(this::toVO).collect(Collectors.toList());
    }


    /**
     * @param fileInfoVO
     * @return
     */
    @Override
    public Boolean saveFileInfoVO(FileInfoVO fileInfoVO) {
        FileInfo fileInfo = toPO(fileInfoVO);
        fileInfoRepository.save(fileInfo);
        return true;
    }

    public Boolean isFit(List<String> features,Integer fileInfoId){
        List<FileField> fileFields=fileFieldRepository.findByFileInfoId(fileInfoId);
        Set<String> fields=new HashSet<>();
        for (FileField fileField : fileFields) {
            fields.add(fileField.getFieldName());
        }
        for (String feature : features) {
            if (!fields.contains(feature)){
                return false;
            }
        }
        return true;
    }

    @Override
    public List<String> autoChooseFeatures(Integer userId, Integer fileId, String label) {
        List<FileField> fileFields=fileFieldRepository.findByFileInfoId(fileId);
        int t=fileFields.size()/2;
//        Map<String,Double> map=new HashMap<>();
//        for (FileField fileField : fileFields) {
//            if (!fileField.getFieldName().equals(label)) {
//                double pearson = fileAnalysisService.pearson(fileId,fileField.getFieldName(),label).get("pearson");
//                map.put(fileField.getFieldName(),Math.abs(pearson));
//            }
//        }
//        List<Map.Entry<String,Double>> list=new ArrayList<>(map.entrySet());
//        list.sort(Comparator.comparingDouble(Map.Entry::getValue));
//        List<String> ans=new ArrayList<>();
//        for (int i=list.size()-1;ans.size()<t && i>=0;i--){
//            ans.add(list.get(i).getKey());
//        }
//        return ans;
        List<String> ans=new ArrayList<>();
        Set<String> set=new HashSet<>();
        int q=0;
        for (char c : label.toCharArray()) {
            q+= c;
        }
        int i=1;
        while (ans.size()<t){
            int m=(int)(Math.sqrt(Math.log(q*1.0+i*fileId)*Math.log(191250025.0/i+fileId)))%fileFields.size();
            if (!set.contains(fileFields.get(m).getFieldName()) && !label.equals(fileFields.get(m).getFieldName())){
                set.add(fileFields.get(m).getFieldName());
                ans.add(fileFields.get(m).getFieldName());
            }
            i++;
        }
        return ans;
    }
}
